package cn.ltgodm.sandbox.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.ltgodm.sandbox.exception.CompliedException;
import cn.ltgodm.sandbox.exception.ExecuteException;
import cn.ltgodm.sandbox.model.dto.ExecuteCodeRequest;
import cn.ltgodm.sandbox.model.dto.ExecuteCodeResponse;
import cn.ltgodm.sandbox.model.dto.ExecuteMessage;
import cn.ltgodm.sandbox.service.CodeSandboxTemplate;
import cn.ltgodm.sandbox.utils.ProcessUtil;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.ExecCreateCmd;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.ExecStartCmd;
import com.github.dockerjava.api.model.Frame;
import com.github.dockerjava.api.model.StreamType;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author ltgodm
 * @date 2024-03-07 10:25:28
 */
@Slf4j
@Service
public class JavaShellSandboxServiceImpl extends CodeSandboxTemplate {
    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
        try{
        //初始化参数
        List<String> inputList = executeCodeRequest.getInputList();
        String code = executeCodeRequest.getCode();
        String language = executeCodeRequest.getLanguage();

        //1.获取dockerClient连接
        DockerClient dockerClient = super.getDockerClient(this.dockerHost);

        //2.启动容器
        String containerId = super.createContainer(language, languageConfigs, dockerClient);

        //3.运行shell脚本 上传文件到docker容器中
            //2.1 判断shell脚本是否存在，如果不存在上传shell脚本
            //2.2 构造出容器保存的路径
            //2.3 将构造出来的路径通过命令传参，运行shell脚本
        String userCodeFilePath = saveUserCodeFile(containerId, dockerClient, code);
        String userCOdeFileName = StrUtil.format("{}/{}.java",userCodeFilePath,className);

        //4.编译代码
        compliedProcess(dockerClient,containerId,userCOdeFileName);

        //5.运行代码
        String[] runCode = {"java", "-cp", userCodeFilePath, "Main"};
        List<ExecuteMessage> executeMessageList = super.runCodeFile(inputList, dockerClient, containerId,runCode);

        //6.清理文件
            //6.1 使用shell脚本删除代码
        removeUserCodeFile(containerId,dockerClient,userCodeFilePath);

        //7.构造返回结果
        ExecuteCodeResponse response = super.getResponse(executeMessageList);
        return response;
        } catch (Exception e) {
            return ProcessUtil.getErrorResponse(e);
        }
    }

    /**
     * 保存用户文件
     * @param containerId 容器id
     * @param dockerClient dockerClient连接
     * @param code 用户代码
     * @return 代码保存目录
     */
    private String saveUserCodeFile(String containerId, DockerClient dockerClient, String code) {
        //TODO 查询shell脚本是否存在
        //1.创建执行脚本
        //生成一个UUID路径
        String uuid = IdUtil.fastSimpleUUID();
        String userCodeFilePath = StrUtil.format("/{}/{}",globalCodePath,uuid);
        String fileName = className.concat(".java");
        String[] cmdArray = new String[]{"sh", StrUtil.format("/{}/saveUserFile.sh",globalCodePath),userCodeFilePath,fileName,code};
        ExecCreateCmd execCreateCmd = dockerClient.execCreateCmd(containerId);
        ExecCreateCmdResponse exec = execCreateCmd
                .withCmd(cmdArray)
                .withAttachStdin(true)
                .withAttachStderr(true)
                .withAttachStdout(true)
                .exec();
        String execId = exec.getId();

        //2.获取执行信息
        ExecStartResultCallback callback = new ExecStartResultCallback(){
            @Override
            public void onNext(Frame frame) {
                StreamType streamType = frame.getStreamType();
                String msg = new String(frame.getPayload());
                if (streamType.equals(StreamType.STDERR)){
                    //错误
                    log.error("保存用户代码文件出错：{}",msg);
                    throw new ExecuteException("保存用户代码文件发生异常："+msg);
                }else{
                    //成功
                    log.info("保存用户代码文件成功：{}",msg);
                }
            }
        };

        //3.执行
        ExecStartCmd execStartCmd = dockerClient.execStartCmd(execId);
        try {
            execStartCmd.exec(callback).awaitCompletion();
        } catch (InterruptedException e) {
            throw new ExecuteException("保存用户代码文件发生异常："+e.getMessage());
        }finally {
            execStartCmd.close();
            execCreateCmd.close();
        }
        //4.返回
        return userCodeFilePath;
    }

    /**
     * 删除用户文件
     * @param containerId 容器id
     * @param dockerClient dockerClient连接
     */
    private void removeUserCodeFile(String containerId, DockerClient dockerClient,String userCodeFilePath) {
        //TODO 查询shell脚本是否存在
        //1.创建执行脚本
        String[] cmdArray = new String[]{"sh", StrUtil.format("/{}/removeUserFile.sh",globalCodePath),userCodeFilePath};
        ExecCreateCmdResponse exec = dockerClient.execCreateCmd(containerId)
                .withCmd(cmdArray)
                .withAttachStdin(true)
                .withAttachStderr(true)
                .withAttachStdout(true)
                .exec();
        String execId = exec.getId();

        //2.获取执行信息
        ExecStartResultCallback callback = new ExecStartResultCallback(){
            @Override
            public void onNext(Frame frame) {
                StreamType streamType = frame.getStreamType();
                String msg = new String(frame.getPayload());
                if (streamType.equals(StreamType.STDERR)){
                    //错误
                    log.error("删除用户代码文件出错：{}",msg);
                    throw new ExecuteException("删除用户代码文件发生异常："+msg);
                }else{
                    //成功
                    log.info("删除用户代码文件成功：{}",msg);
                }
            }
        };

        //3.执行
        try {
            dockerClient.execStartCmd(execId).exec(callback).awaitCompletion();
        } catch (InterruptedException e) {
            throw new ExecuteException("删除用户代码文件发生异常："+e.getMessage());
        }
    }

    /**
     * 编译代码
     * @param dockerClient dockerClient客户端
     * @param containerId 容器id
     * @param userCodeName 用户代码路径
     */
    private void compliedProcess(DockerClient dockerClient,String containerId,String userCodeName) throws CompliedException{
        ExecuteMessage executeMessage = new ExecuteMessage();
        //1.创建执行脚本
        String[] cmdArray = new String[]{"javac","-encoding",charset,userCodeName};
        ExecCreateCmdResponse exec = dockerClient.execCreateCmd(containerId)
                .withCmd(cmdArray)
                .withAttachStdin(true)
                .withAttachStderr(true)
                .withAttachStdout(true)
                .exec();
        String execId = exec.getId();

        //2.获取执行信息
        ExecStartResultCallback callback = new ExecStartResultCallback(){
            @Override
            public void onNext(Frame frame) {
                StreamType streamType = frame.getStreamType();
                String msg = new String(frame.getPayload());
                if (streamType.equals(StreamType.STDERR)){
                    //错误
                    log.error("编译用户代码文件出错：{}",msg);
                    throw new CompliedException("编译用户代码文件发生异常："+msg);
                }else{
                    //成功
                    log.info("编译用户代码文件成功：{}",msg);
                }
            }
        };

        //3.执行
        try {
            dockerClient.execStartCmd(execId).exec(callback).awaitCompletion();
        } catch (InterruptedException e) {
            throw new CompliedException("编译用户代码文件发生异常："+e.getMessage());
        }
    }
}
